For this project, I use NYC Open Data to alleviate my anxieties about living on the top floor of an apartment building and investigate serious incidents requiring the fire department to respond. Using data about the locations of Fire Stations and fires occurring in New York City, I look into whether response times to fires differ across the city. Lastly, I focus on whether distance from a Fire Station affects response times to the scene.
NYC Open Data has data on all incidents responded to by fire companies. The following variables are available:
This dataset is only updated annually, and thus far only data from 2013 to 2015 is contained. The following analyses are run using just 2015 data and only the most severe fires (Level 7).
# data manipulation (by Thomas Brambor)
fire <- read_csv("./data/Incidents_Responded_to_by_Fire_Companies.csv")
fire$year <- substr(fire$INCIDENT_DATE_TIME, 7, 10)
fire <- fire%>%
filter(HIGHEST_LEVEL_DESC == "7 - Signal 7-5") %>%
filter(year==2015)
# Geocoding data
# Make list of addresses
address <- str_c( str_to_title(fire$STREET_HIGHWAY),
"New York, NY",
fire$ZIP_CODE,
sep=", ")
# Register Google API Key
register_google(key = Sys.getenv("GOOGLE_MAPS_API_KEY"))
# Geocode Addresses
latlong <- geocode(address, output = c("latlon"))
# Merge on
fire$Latitude <- latlong$lat
fire$Longitude <- latlong$lon
# Save File
write_csv(fire, "severe_incidents.csv")NYC Open Data also provides data on the location of all 218 Fire Stations in NYC. Relevant to my analyses are the following variables:
FacilityName, Borough, Latitude, Longitude
In addition, NYC Open Data provides shape files for NYC Borough boundaries. This data is used in the final few plots.
# Read in both datasets
library(tidyverse)
fire <- read_csv("../data/severe_incidents.csv")
locs <- read_csv("../data/FDNY_Firehouse_Listing.csv")
locs <- locs %>%
select(FacilityName, Borough, Latitude, Longitude)Below is a leaflet map of the severe fires contained in the file severe_incidents.csv. Fires that occurred at locations outside the five boroughs of New York City were excluded. The type of incident, date and time of incident, where the fire spread, the number of firemen/women units that came onto scene, whether a detector was present and the total duration of the incident are available as information in interactive popups.
library(leaflet)
library(RColorBrewer)
# rid of outliers
outliers <- filter(fire,Latitude == 40.4520226 & Longitude == -74.8161585|Latitude == 40.7436253 & Longitude == -73.6580927|Latitude == 40.701487 & Longitude == -73.349799|Latitude == 40.9206623 & Longitude == -73.787364|Latitude == 40.9179949 & Longitude == -73.8956178)
fire <- fire[!(fire$IM_INCIDENT_KEY %in% outliers$IM_INCIDENT_KEY),]
# popups
fire$INCIDENT_TYPE_DESC <- substring(fire$INCIDENT_TYPE_DESC, 7) # rid of numbers
fire$FIRE_SPREAD_DESC <- substring(fire$FIRE_SPREAD_DESC, 5)
fire$DETECTOR_PRESENCE_DESC <- substring(fire$DETECTOR_PRESENCE_DESC, 5)
fire$TOTAL_INCIDENT_DURATION <- round(fire$TOTAL_INCIDENT_DURATION/60, 2) # convert to minutes
content <- paste("<b>What:</b>",fire$INCIDENT_TYPE_DESC,"<br/>",
"<b>When</b>:",fire$INCIDENT_DATE_TIME,"<br/>",
"<b>Location</b>:",fire$FIRE_SPREAD_DESC,"<br/>",
"<b>Length of Incident (min)</b>:", fire$TOTAL_INCIDENT_DURATION, "<br/>",
"<b>Number of Units On Scene</b>:",fire$UNITS_ONSCENE,"<br/>",
"<b>Fire Dectector Status</b>:", fire$DETECTOR_PRESENCE_DESC, "<br/>")
fireIcons <- icons(
iconUrl = "../images/fire.png",
iconWidth = 8, iconHeight = 11
#iconAnchorX = 22, iconAnchorY = 94
)
# legend
html_legend <- "2015 NYC Fires<br> <img src= 'http://icons.iconarchive.com/icons/fatcow/farm-fresh/16/fire-icon.png'> "
# map
leaflet(data = fire, options = leafletOptions(minZoom = 10)) %>%
setView(lat = 40.730610, lng = -73.935242, zoom = 10) %>%
addTiles() %>%
addProviderTiles("CartoDB.Positron") %>%
addMarkers(lat = ~Latitude, lng = ~Longitude,
icon = fireIcons, popup = content) %>%
addControl(html = html_legend, position = "bottomright")Out of curiosity, I created a heatmap of the same most severe incidents to investigate whether certain areas in NYC had more 2015 fires than others. From the map we can see Manhattan, the Bronx, and some of Brooklyn experienced the most severe incidents while Queens and Long Island didn’t experience quite as many. This is likely due to population density differences between these boroughs.
incident_map <- get_map(location = c(lon = -73.935242, lat = 40.730610), maptype = "toner-lite", zoom = 10)
ggmap(incident_map) +
geom_density2d(aes(x = Longitude, y = Latitude),
data = fire,
color="blue",
size=0.5,
bins=10) +
stat_density2d(aes(x = Longitude, y = Latitude,
fill = ..level..,
alpha = ..level..),
data = fire,
geom = 'polygon',
bins = 10,
show.legend = FALSE) +
scale_fill_gradient2(low = "yellow", mid = "orange", high = "red") +
scale_alpha(range = c(0.00, 0.5)) +
labs(x = '', y = '') +
theme(axis.text = element_blank()) +
theme(axis.ticks = element_blank())Beginning with the previous leaflet map, I distinguished the markers of the fire locations by PROPERTY_USE_DESC, i.e. what kind of property was affected. There were many categories of properties, so I rounded them down as much as possible while still providing interesting information. Information about the property type as well the same details about the incidents are recorded in interactive popups. AND I changed the map to black for dramatic effect.
### Data Cleaning
fire$PROPERTY_USE_DESC <- substring(fire$PROPERTY_USE_DESC, 7) # rid of numbers before prop name
tab <- as.data.frame(table(fire$PROPERTY_USE_DESC)) # table to get frequency
colnames(tab) <- c("PROPERTY_USE_DESC", "Freq")
fireprop <- merge(x = fire, y = tab, by = "PROPERTY_USE_DESC", all = TRUE) # merge df and table
fireprop$PROPERTY_USE_DESC <- ifelse(fireprop$Freq < 3, "Other", fireprop$PROPERTY_USE_DESC) # all property types with fewer than 10 occurences become "other"
## Renaming and Classifying
# Open spaces
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Open land or field"|fireprop$PROPERTY_USE_DESC=="Outside or special property, other"|fireprop$PROPERTY_USE_DESC=="Dock, marina, pier, wharf"|fireprop$PROPERTY_USE_DESC=="Forest, timberland, woodland"] <- "Open Land/Field"
# schools
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Schools, non-adult, other"|fireprop$PROPERTY_USE_DESC=="Adult education center, college classroom"] <- "School"
# residential spaces
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Residential, other"|fireprop$PROPERTY_USE_DESC=="1 or 2 family dwelling"|fireprop$PROPERTY_USE_DESC=="Multifamily dwelling"|fireprop$PROPERTY_USE_DESC=="Dormitory-type residence, other"] <- "Residential Home"
# streets
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Street, other"|fireprop$PROPERTY_USE_DESC=="Residential street, road or residential driveway"|fireprop$PROPERTY_USE_DESC=="Street or road in commercial area"|fireprop$PROPERTY_USE_DESC=="Railroad right-of-way"|fireprop$PROPERTY_USE_DESC=="Highway or divided highway"] <- "Street/Highway"
# parking garages
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Parking garage, (detached residential garage)"| fireprop$PROPERTY_USE_DESC=="Vehicle parking area"|fireprop$PROPERTY_USE_DESC=="Vehicle storage, other"] <- "Parking Garage"
# hotels
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Hotel/motel, commercial"] <- "Hotel/Motel"
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Boarding/rooming house, residential hotels"] <- "Hotel/Motel"
# businesses
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Mercantile, business, other"] <- "Mercantile Business"
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Laundry, dry cleaning"] <- "Mercantile Business"
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Business office"] <- "Business Office"
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="General retail, other"] <- "Mercantile Business"
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Specialty shop"] <- "Mercantile Business"
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Professional supplies, services"] <- "Mercantile Business"
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Food and beverage sales, grocery store"] <- "Mercantile Business"
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Convenience store"] <- "Mercantile Business"
# restaurants
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Restaurant or cafeteria"|fireprop$PROPERTY_USE_DESC=="Eating, drinking places, other"|fireprop$PROPERTY_USE_DESC=="Bar or nightclub"] <- "Restaurant/Bar"
# warehouses
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Property Use, other"|fireprop$PROPERTY_USE_DESC=="Storage, other"|fireprop$PROPERTY_USE_DESC=="Warehouse"] <- "Warehouse/Storage"
# others
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Hospital - medical or psychiatric"] <- "Hospital"
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Motor vehicle or boat sales, services, repair"|fireprop$PROPERTY_USE_DESC=="Service station, gas station"] <- "Gas Station/Vehicle Repair"
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Rapid transit station"] <- "Transit Station"
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Manufacturing, processing"|fireprop$PROPERTY_USE_DESC=="Outbuilding or shed"| fireprop$PROPERTY_USE_DESC=="Undetermined"| fireprop$PROPERTY_USE_DESC=="Industrial plant yard - area"] <- "Other"
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Church, mosque, synagogue, temple, chapel"] <- "Religious Facilities"
fireprop$PROPERTY_USE_DESC[fireprop$PROPERTY_USE_DESC=="Construction site"] <- "Construction Site"
outdoors <- c("Open Land/Field", "Street/Highway")
civic <- c( "School", "Parking Garage", "Religious Facilities", "Transit Station", "Hospital", "Construction Site", "Warehouse/Storage")
residential <- c("Residential Home")
commercial <- c("Restaurant/Bar","Hotel/Motel","Gas Station/Vehicle Repair", "Mercantile Business", "Business Office")
other <- c("Other")
# round down categories
fireprop$property <- ifelse(fireprop$PROPERTY_USE_DESC %in% residential, "Residential", 0)
fireprop$property <- ifelse(fireprop$PROPERTY_USE_DESC %in% civic , "Civic", fireprop$property)
fireprop$property <- ifelse(fireprop$PROPERTY_USE_DESC %in% commercial, "Commercial", fireprop$property)
fireprop$property <- ifelse(fireprop$PROPERTY_USE_DESC %in% outdoors, "Outdoors", fireprop$property)
fireprop$property <- ifelse(fireprop$PROPERTY_USE_DESC %in% other, "Other", fireprop$property)
### Map
# create order of factor based on how many incidents occured in that type of property
fireprop$property <- factor((fireprop$property), levels = c("Residential", "Commercial", "Civic", "Outdoors", "Other"))
colorCount <- length(unique(fireprop$property)) # number of levels
binpal <- colorFactor(c("#800026","#E31A1C", "#FD8D3C","#FED976","#ffffff"), fireprop$property)
# popups
content <- paste("<b>What:</b>",fireprop$INCIDENT_TYPE_DESC,"<br/>",
"<b>When:</b>",fireprop$INCIDENT_DATE_TIME,"<br/>",
"<b>Where:</b>",fireprop$PROPERTY_USE_DESC,"<br/>", # added where
"<b>Location:</b>",fireprop$FIRE_SPREAD_DESC,"<br/>",
"<b>Length of Incident (min):</b>", fireprop$TOTAL_INCIDENT_DURATION, "<br/>",
"<b>Number of Units On Scene:</b>",fireprop$UNITS_ONSCENE,"<br/>",
"<b>Fire Dectector Status:</b>", fireprop$DETECTOR_PRESENCE_DESC, "<br/>")
# map -- starting with other map but changed data source
leaflet(data = fireprop, options = leafletOptions(minZoom = 9)) %>%
setView(lat = 40.730610, lng = -73.935242, zoom = 9) %>%
addTiles() %>%
addProviderTiles("CartoDB.DarkMatter") %>%
addCircles(lat = ~Latitude, lng = ~Longitude,
color = ~binpal(property),
weight = 5,
opacity = 0.7,
fill = TRUE,
fillOpacity = 0.5,
popup = content ) %>%
addLegend("bottomright",
colors = c("#800026","#E31A1C", "#FD8D3C","#FED976","#ffffff"),
values = ~fireprop$property,
title = "Incident Property Type",
labels = c("Residential", "Commercial", "Civic", "Outdoors", "Other"))I added marker clustering, so that zooming in reveals the individual locations but the zoomed out map only shows the clusters.
leaflet(data = fireprop, options = leafletOptions(minZoom = 10)) %>%
setView(lat = 40.730610, lng = -73.935242, zoom = 10) %>%
addTiles() %>%
addProviderTiles("CartoDB.DarkMatter") %>%
addCircleMarkers(lat = ~Latitude, lng = ~Longitude,
color = colors,
popup = content,
clusterOptions = markerClusterOptions()) %>%
addLegend("bottomright",
colors = c("#FFFFCC", "#FED976", "#FD8D3C", "#E31A1C", "#800026"),
values = ~fireprop$property,
title = "Incident Property Type",
labels = c("Residential", "Commercial", "Civic", "Outdoors", "Other"))Next, I adjusted the initial leaflet map such that the size of the circle markers indicate severity (by the number of units that came on to the scene) and colors indicated the length of time the fire persisted. Fire station locations were also added. There are two layers to the map (“Incidents”, “Firehouses”) that allow the user to select which information to show.
# colors
fireprop$brks <- cut(fireprop$TOTAL_INCIDENT_DURATION,
breaks=c(0, 75, 110, 120, 150, 7140),
labels=c("10-75 min", "1.25-1.8 hrs", "1.8-2 hrs",
"2-2.5 hrs", ">2.5 hrs"))
bins <- c(0, 75, 110, 120, 150, 7140)
pal <- colorBin("YlOrRd", domain = fireprop$TOTAL_INCIDENT_DURATION, bins = bins)
color <- pal(fireprop$TOTAL_INCIDENT_DURATION)
# icon
firehouseIcons <- icons(
iconUrl = "http://icons.iconarchive.com/icons/icons8/windows-8/48/Maps-Marker-icon.png",
iconWidth = 15, iconHeight = 20
)
# popup content
content <- paste("<b>What:</b>",fireprop$INCIDENT_TYPE_DESC,"<br/>",
"<b>When:</b>",fireprop$INCIDENT_DATE_TIME,"<br/>",
"<b>Where:</b>",fireprop$PROPERTY_USE_DESC,"<br/>", # added where
"<b>Location:</b>",fireprop$FIRE_SPREAD_DESC,"<br/>",
"<b>Length of Incident:</b>", fireprop$brks, "<br/>",
"<b>Number of Units On Scene:</b>",fireprop$UNITS_ONSCENE,"<br/>",
"<b>Fire Dectector Status:</b>", fireprop$DETECTOR_PRESENCE_DESC, "<br/>")
# new content for firehouse popups
loc_content <- paste("<b>Facility Name:</b>",locs$FacilityName,"<br/>",
"<b>Borough:</b>", locs$Borough, "<br/>")
# map
leaflet(options = leafletOptions(minZoom = 8)) %>%
setView(lat = 40.730610, lng = -73.935242, zoom = 10) %>%
addTiles() %>%
addProviderTiles("CartoDB.Positron", group = "New York City") %>%
addCircleMarkers(data = fireprop,
lat = ~Latitude, lng = ~Longitude,
color = color,
opacity = 0.8,
fillOpacity = 0.7,
weight = 1,
radius = ~fireprop$UNITS_ONSCENE/1.8, # make radius smaller
popup = content,
group = "Incidents") %>%
addMarkers(data = locs,
lat = ~Latitude, lng = ~Longitude,
popup = loc_content,
icon = firehouseIcons,
group = "Fire Stations") %>%
addLegend("bottomright",
colors = c("#FFFFB2", "#FECC5C", "#FD8D3C", "#F03B20", "#BD0026"),
values = ~fireprop$brks,
title = "Length of Incident",
labels = c("10-75 min", "1.25-1.8 hrs", "1.8-2 hrs",
"2-2.5 hrs", ">2.5 hrs")) %>%
addLayersControl(
baseGroups = "New York City",
overlayGroups = c("Incidents", "Fire Stations"))I then investigated whether the distance of the incident from the nearest Fire Station varied across the city. For all incident locations, I identified the nearest fire station and calculated the distance between that station and the incident location. Below is a look at the dataframe generated with the fire station deemed closest to the incident and the calculated approximate distance (mi).
library(geosphere)
# Fire Station locations lat, lon
fh_loc <- locs[ , 3:4]
colnames(fh_loc) <- c("lat", "lon")
fh_loc <- na.omit(fh_loc)
# incident locations lat, lon
inc_loc <- fire[ , c(25:26,1)]
colnames(inc_loc) <- c("lat", "lon", "IM_INCIDENT_KEY")
inc_loc <- na.omit(inc_loc)
# calculating distance between incident and nearest Fire Station
# break up into chunks of 213 due to issues with dfs being different sizes
set1 <- inc_loc[1:213, ]
set2 <- inc_loc[214:426, ]
set3 <- inc_loc[427:639, ]
set4 <- inc_loc[640:852, ]
set5 <- inc_loc[853:1065, ]
set6 <- inc_loc[1066:1278, ]
set7 <- inc_loc[1279:1491, ]
set8 <- inc_loc[1492:1704, ]
set9 <- inc_loc[1705:1917, ]
set10 <- inc_loc[1918:2130, ]
set11 <- rbind(inc_loc[2131:2319, ], inc_loc[1:24,])
sets <- list(set1, set2, set3, set4, set5, set6, set7, set8, set9, set10, set11)
df <- NULL
for (i in sets) {
incidents <- i
i$nearest_fh_ID <- which.min(distGeo(incidents, fh_loc))
i$distance <- (min(distGeo(incidents, fh_loc)) * 0.000621371) # convert to miles
df<-rbind(df,i)
}
firehouse_incidents <- df[-(2319:2342),] # rid of dummy data
rownames(firehouse_incidents) <- 1:nrow(firehouse_incidents) # reset index
# time until firetruck arrived
full_fire <- merge(x = firehouse_incidents, y = fireprop, by = "IM_INCIDENT_KEY", all = TRUE)
full_fire$INCIDENT_DATE_TIME <- strptime(full_fire$INCIDENT_DATE_TIME, "%m/%d/%Y %I:%M:%S %p")
full_fire$ARRIVAL_DATE_TIME <- strptime(full_fire$ARRIVAL_DATE_TIME, "%m/%d/%Y %I:%M:%S %p")
full_fire$diff_time <- as.numeric(difftime(full_fire$ARRIVAL_DATE_TIME,
full_fire$INCIDENT_DATE_TIME,
tz = "EST",
units = "mins"))
full_fire <- merge(x = locs, y = full_fire, by.x = 0, by.y = "nearest_fh_ID", all = TRUE)
full_fire[5:10, c("PROPERTY_USE_DESC", "Borough", "FacilityName", "distance")]## PROPERTY_USE_DESC Borough FacilityName distance
## 5 Residential Home Brooklyn Engine 222/Battalion 37 0.2480061
## 6 Residential Home Brooklyn Engine 222/Battalion 37 0.2480061
## 7 Residential Home Brooklyn Engine 222/Battalion 37 0.2480061
## 8 Residential Home Brooklyn Engine 222/Battalion 37 0.2480061
## 9 Residential Home Brooklyn Engine 222/Battalion 37 0.2480061
## 10 Other Brooklyn Engine 222/Battalion 37 0.2480061
First, I visualized a simple scatter plot to see the general distribution of responsiveness. Overall, incidents are responded to quite quickly in NYC with most firefighters arriving at the scene within 10 minutes. I noticed one extreme outlier (an incident that took over an hour to arrive to in an open field) so eliminated that one point for more meaningful insights.
library(ggthemes)
ggplot(full_fire[!(full_fire$diff_time > 40), ], aes(x = IM_INCIDENT_KEY, y = diff_time)) +
geom_point(aes(color = diff_time)) +
scale_color_gradientn(colors = rev(brewer.pal(4,"YlOrRd"))) +
labs(y = "Arrival Time (min)",
x = "Fire Incidents",
title = "NYC Fire Responders: 2015",
subtitle = "Arrival times quick & relatively stable") +
theme_tufte() +
theme(axis.text.x = element_blank()) +
theme(axis.ticks.x = element_blank()) +
theme(legend.position = "none")Next, I looked at whether arrival times differed depending on the property type. I thought units would be more responsive to homes/residences in general, but it turns out there’s not much of a difference in how quickly rescuers arrive based on property type. There were a few more incidences of rescuers arriving to fires in open fields and highways a bit later than they would at a residence or restaurant, though, based on this scatter plot, a restaurant/bar would be the place to be in a fire in terms of reliability of firefighters arrivals.
ggplot(data = full_fire[!(full_fire$diff_time > 40|is.na(full_fire$PROPERTY_USE_DESC)), ], aes(x = reorder(PROPERTY_USE_DESC, diff_time), y = diff_time)) +
geom_point(aes(color = diff_time)) +
scale_color_gradientn(colors = rev(brewer.pal(3,"YlOrRd"))) +
labs(y = "Arrival Time (min)", x = '',
title = "2015 NYC Fire Responders",
subtitle = "Arrival times to incidents by property type") +
coord_flip() +
theme_tufte() +
theme(legend.position = "none")Next, I visualized firefighter arrival times to incidents by NYC borough. Again, there’s not too much variation between the boroughs but there are a few incidents in Staten Island, Queens and Manhattan that were long waits for help.
colors <- c("#800026","#E31A1C", "#FD8D3C","#FED976","#ffffff")
full_fire$BOROUGH_DESC <-substring(full_fire$BOROUGH_DESC, 5)
ggplot(full_fire[!is.na(full_fire$BOROUGH_DESC), ], aes(x = as.factor(BOROUGH_DESC), y = diff_time)) +
geom_violin(aes(fill = BOROUGH_DESC, alpha = 0.8)) +
labs(y = "Arrival Time (min)", x = "",
title = "2015 NYC Fire Responders",
subtitle = "Arrival times to incidents by NYC borough: Bronx & Brooklyn the places to be") +
coord_flip() +
theme_tufte() +
theme(legend.position = "none")I wanted to see whether distance from one’s nearest fire station mattered in terms of how quickly responders arrive to the scene. Oddly, it doesn’t seem like distance to a fire station matters in terms of how fast help will arrive. Luckily, in NYC nobody is more than 0.65 miles away from a fire station, so I wonder if I’d see a larger difference in arrival time in a city/suburb with fewer stations.
ggplot(full_fire[!(full_fire$diff_time > 40|is.na(full_fire$distance)), ], aes(x = distance, y = diff_time)) +
geom_point(aes(color = diff_time)) +
scale_color_gradientn(colors = rev(brewer.pal(3,"YlOrRd"))) +
labs(y = "Arrival Time (min)",
x = "Distance from Nearest Fire Station (mi)",
title = "2015 NYC Fire Responders",
subtitle = "Arrival times quick & stable regardless of distance to incident") +
theme_tufte() +
theme(legend.position = "none")Next, I created map visualizations of the duration the fire incident and the time it took for a unit to arrive by distance to the nearest fire station. I differentiated by the incident’s distance from the nearest fire station and colored each property by the duration of the incident. The maps make it clear that there are far fewer locations that are even more than a half mile away from a fire station as opposed to less than half a mile (and even more properties are within a quarter mile from a fire station!). The duration of a fire likely depends on other aspects I didn’t have access to in this dataset.
# categories of distance to fire station
full_fire$distance_rd <- ifelse(full_fire$distance < .25,
"Closer Than Quarter Mile",
full_fire$distance)
full_fire$distance_rd <- ifelse(full_fire$distance >= .25 & full_fire$distance < .50,
"Between Quarter & Half Mile",
full_fire$distance_rd)
full_fire$distance_rd <- ifelse(full_fire$distance >= .5,
"Further Than Half Mile",
full_fire$distance_rd)
full_fire$distance_rd <- factor(full_fire$distance_rd, levels = c("Closer Than Quarter Mile", "Between Quarter & Half Mile", "Further Than Half Mile"))
# breaks in fire length
full_fire$brks <- cut(full_fire$TOTAL_INCIDENT_DURATION,
breaks=c(0, 75, 110, 120, 150, 7140),
labels=c("10-75 min", "1.25-1.8 hrs", "1.8-2 hrs",
"2-2.5 hrs", ">2.5 hrs"))
# map
ggmap(incident_map) +
geom_point(data = full_fire[!is.na(full_fire$distance_rd), ],
aes(x = lon, y = lat, color = brks),
alpha = 0.5,
size = 1) +
scale_color_brewer(name = "Length of Fire:",
type = "seq",
palette = "YlOrRd",
direction = 1,
guide = "legend") +
labs(x = '', y = '',
title = "NYC Fire Incidents & Proximity to Nearest Fire Station",
subtitle = "2015") +
theme_tufte() +
theme(axis.text = element_blank()) +
theme(axis.ticks = element_blank()) +
facet_wrap(~distance_rd, nrow = 1, drop = TRUE) +
theme(strip.background = element_rect(fill = "#ffff99")) +
theme(legend.position = "right")Moreover, fire units tend to arrive in a quick manner regardless of their distance to the incident. This relieved my anxieties more than anything else, but we’ll see below that some boroughs get a slightly quicker response by the fire unit on average than others.
full_fire$brks_time <- cut(full_fire$diff_time,
breaks=c(0, 1, 2.5, 3.5, 5, 10, 40),
labels=c("0-1 min", "1-2.5 min", "2.5-3.5 min",
"3.5-5 min", "5-10 min", ">10 min"))
ggmap(incident_map) +
geom_point(data = full_fire[!is.na(full_fire$distance_rd)&!(full_fire$diff_time > 40), ],
aes(x = lon, y = lat, color = brks_time),
alpha = 0.5,
size = 1) +
scale_color_brewer(name = "Arrival Time:",
type = "seq",
palette = "YlOrRd",
direction = 1,
guide = "legend") +
labs(x = '', y = '',
title = "2015 NYC Fires",
subtitle = "Fire Unit Arrival Time & Proximity to Incident") +
theme_tufte() +
theme(axis.text = element_blank()) +
theme(axis.ticks = element_blank()) +
facet_wrap(~distance_rd, nrow = 1, drop = TRUE) +
theme(strip.background = element_rect(fill = "#ffff99")) +
theme(legend.position = "right")When broken out into boroughs, it seems like even though Brooklyn and Queens have many fires, a lot of them are short lived. Staten Island seems to have had the fewest fires, though some lasted quite a while. Let’s remember, no matter the borough, most fire incidents are within a half mile of a fire station! We’re all safe (ish).
library(rgdal)
nyc_bor <- readOGR("../data/nybb_18a/.", "nybb")## OGR data source with driver: ESRI Shapefile
## Source: "../data/nybb_18a/.", layer: "nybb"
## with 5 features
## It has 4 fields
nyc_bor <- spTransform(nyc_bor,
CRS("+proj=longlat +datum=WGS84"))
nyc_bor <- fortify(nyc_bor)
nyc_bor$id[nyc_bor$id == 0] <- "Staten Island"
nyc_bor$id[nyc_bor$id == 1] <- "Manhatten"
nyc_bor$id[nyc_bor$id == 2] <- "Bronx"
nyc_bor$id[nyc_bor$id == 3] <- "Brooklyn"
nyc_bor$id[nyc_bor$id == 4] <- "Queens"
nyc_bor$Borough <- nyc_bor$id
map_bor <- get_map("New York City", zoom = 10, maptype = "toner-background")
ggmap(map_bor) +
geom_polygon(aes(fill = Borough, x = long, y = lat, group = group),
data = nyc_bor,
alpha = 0.6,
color = "black",
size = 0.2) +
geom_point(data = full_fire[!is.na(full_fire$distance_rd), ],
aes(x = lon, y = lat, color = brks),
alpha = 0.9,
size = 0.3) +
scale_color_brewer(name = "Length of Fire:",
type = "seq",
palette = "YlOrRd",
direction = 1,
guide = "legend") +
labs(x = '', y = '',
title = "2015 NYC Fires & Duration of Incident",
subtitle = "By Borough") +
theme_tufte() +
theme(axis.text = element_blank()) +
theme(axis.ticks = element_blank())Lastly, I visualized the five NYC boroughs by the average time it took for fire units to show up to the scene. While certainly a small difference, it takes fire units the longest to arrive to Staten Island in comparison to the other boroughs, likely because (as we’ve seen), Staten Island has fewer stations than the other boroughs, and it could be tricky for Manhattan units to get across the bridge quickly.
test <- full_fire %>%
select(BOROUGH_DESC, diff_time) %>%
na.omit() %>%
group_by(BOROUGH_DESC) %>%
summarize(avg = mean(diff_time))
test1 <- merge(nyc_bor, test, by.x = "Borough", by.y = "BOROUGH_DESC")
ggmap(map_bor) +
geom_polygon(aes(fill = avg, x = long, y = lat, group = group),
data = test1,
alpha = 0.6,
color = "black",
size = 0.2) +
scale_fill_gradientn(colors =(brewer.pal(5,"YlOrRd")), name = "Avg. Arrival Time (min)") +
labs(x = '', y = '',
title = "2015 NYC Fires & Avg. Arrival Time to Incident",
subtitle = "By Borough") +
theme_tufte() +
theme(axis.text = element_blank()) +
theme(axis.ticks = element_blank())